﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Actors;
using System.Threading.Tasks;

namespace System.Cloud.Collections
{
    // @TODO: INotifyCollectionChanged?
    public class CloudDictionary<TKey, TValue> : IDictionaryAsync<TKey, TValue>, IDictionary<TKey, TValue>, IDisposable
    {
        private CloudStringDictionary<TValue> _stringDictionary;
        private Func<TKey, String> _toString;
        private Func<String, TKey> _fromString;

        public CloudDictionary(String hostEndpoint, String dictionaryName, Func<TKey, String> toIdentityString = null,
            Func<String, TKey> fromIdentityString = null, /*CancellationToken cancellationToken,*/ bool connectThroughGateway=false)
        {
            Contract.Requires(!String.IsNullOrEmpty(hostEndpoint));
            Contract.Requires(!String.IsNullOrEmpty(dictionaryName));

            _toString = toIdentityString ?? new Func<TKey, String>((key) => SerializationHelper.Serialize(key));
            _fromString = fromIdentityString ?? new Func<String, TKey>((s) => SerializationHelper.DeserializeToType<TKey>(s));

            _stringDictionary = new CloudStringDictionary<TValue>(hostEndpoint, dictionaryName, connectThroughGateway);

            // Deploy TKey's assembly to the server, if necessary.
            // @TODO: Look at types these type parameters depends on.  IE, is TKey a Tuple<UserType1, UserType2>?
            // UPDATE: .NET type information is no longer needed on the actor.  The actor stores pure JSON.
            //_stringDictionary._actorClient.DeployAssemblyIfNeeded(typeof(TKey).GetTypeInfo().Assembly);
        }

        public CloudDictionary(String hostEndpoint, String partitionedServiceName, String dictionaryName, int numPartitions,
            Func<TKey, String> toIdentityString = null, Func<String, TKey> fromIdentityString = null, 
            /*CancellationToken cancellationToken,*/ bool connectThroughGateway=false)
        {
            Contract.Requires(!String.IsNullOrEmpty(hostEndpoint));
            Contract.Requires(!String.IsNullOrEmpty(dictionaryName));
            Contract.Requires(!String.IsNullOrEmpty(partitionedServiceName));
            Contract.Requires(numPartitions >= 1, "Actors must have one or more partitions.");

            _toString = toIdentityString ?? new Func<TKey, String>((key) => SerializationHelper.Serialize(key));
            _fromString = fromIdentityString ?? new Func<String, TKey>((s) => SerializationHelper.DeserializeToType<TKey>(s));

            _stringDictionary = new CloudStringDictionary<TValue>(hostEndpoint, partitionedServiceName, dictionaryName, numPartitions, connectThroughGateway);

            // Deploy TKey's assembly to the server, if necessary.
            // @TODO: Look at types these type parameters depends on.  IE, is TKey a Tuple<UserType1, UserType2>?
            // UPDATE: .NET type information is no longer needed on the actor.  The actor stores pure JSON.
            //_stringDictionary._actorClient.DeployAssemblyIfNeeded(typeof(TKey).GetTypeInfo().Assembly);
        }


        #region Lifetime management

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_stringDictionary != null) {
                if (disposing)
                    _stringDictionary.Dispose();
                _stringDictionary = null;
            }
        }

        // Destroys the dictionary stored in the Cloud
        public void Destroy()
        {
            _stringDictionary.Destroy();
            this.Dispose();
        }

        #endregion Lifetime management

        #region IAsyncDictionary<TKey, TValue> members

        public TimeSpan Timeout {
            get;
            set;
        }

        public Task<TValue> GetValueAsync(TKey key)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            Contract.EndContractBlock();

            return _stringDictionary.GetValueAsync(_toString(key));
        }

        public Task SetValueAsync(TKey key, TValue value)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            Contract.EndContractBlock();

            return _stringDictionary.SetValueAsync(_toString(key), value);
        }

        public Task<Tuple<bool, TValue>> TryGetValueAsync(TKey key)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            Contract.EndContractBlock();

            return _stringDictionary.TryGetValueAsync(_toString(key));
        }

        public Task<bool> ContainsKeyAsync(TKey key)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            Contract.EndContractBlock();

            return _stringDictionary.ContainsKeyAsync(_toString(key));
        }

        public Task<bool> RemoveAsync(TKey key)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            Contract.EndContractBlock();

            return _stringDictionary.RemoveAsync(_toString(key));
        }

        public Task<ICollection<TValue>> GetValuesAsync(IEnumerable<TKey> keys)
        {
            if (keys == null)
                throw new ArgumentNullException("keys");
            Contract.EndContractBlock();

            String[] keysAsStrings = new String[keys.Count()];
            int i = 0;
            foreach (TKey key in keys)
                keysAsStrings[i++] = _toString(key);
            return _stringDictionary.GetValuesAsync(keysAsStrings);
        }

        public Task SetValuesAsync(IEnumerable<TKey> keys, IEnumerable<TValue> values)
        {
            if (keys == null)
                throw new ArgumentNullException("keys");
            if (values == null)
                throw new ArgumentNullException("values");
            Contract.EndContractBlock();

            String[] keysAsStrings = new String[keys.Count()];
            int i = 0;
            foreach (TKey key in keys)
                keysAsStrings[i++] = _toString(key);
            return _stringDictionary.SetValuesAsync(keysAsStrings, values);
        }

        public Task RemoveAsync(IEnumerable<TKey> keys)
        {
            if (keys == null)
                throw new ArgumentNullException("keys");
            Contract.EndContractBlock();

            String[] keysAsStrings = new String[keys.Count()];
            int i = 0;
            foreach (TKey key in keys)
                keysAsStrings[i++] = _toString(key);
            return _stringDictionary.RemoveAsync(keysAsStrings);
        }

        public ICollection<TKey> Keys
        {
            get {
                ICollection<String> keysAsStrings = _stringDictionary.Keys;
                TKey[] keys = new TKey[keysAsStrings.Count];
                int i = 0;
                foreach(String keyString in keysAsStrings)
                    keys[i++] = _fromString(keyString);
                return keys;
            }
        }

        public ICollection<TValue> Values
        {
            get { return _stringDictionary.Values; }
        }

        public Task<int> CountAsync
        {
            get { return _stringDictionary.CountAsync; }
        }

        public Task<bool> IsReadOnlyAsync
        {
            get { return _stringDictionary.IsReadOnlyAsync; }
        }

        Task ICollectionAsync<KeyValuePair<TKey, TValue>>.AddAsync(KeyValuePair<TKey, TValue> item)
        {
            if (item.Key == null)
                throw new ArgumentNullException("item.Key");
            Contract.EndContractBlock();

            return _stringDictionary.AddAsync(new KeyValuePair<String, TValue>(_toString(item.Key), item.Value));
        }

        public Task ClearAsync()
        {
            return _stringDictionary.ClearAsync();
        }

        Task<bool> ICollectionAsync<KeyValuePair<TKey, TValue>>.ContainsAsync(KeyValuePair<TKey, TValue> item)
        {
            if (item.Key == null)
                throw new ArgumentNullException("item.Key");
            Contract.EndContractBlock();

            return _stringDictionary.ContainsKeyAsync(_toString(item.Key));
        }

        Task ICollectionAsync<KeyValuePair<TKey, TValue>>.CopyToAsync(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            if (array == null)
                throw new ArgumentNullException("array");
            if (arrayIndex < 0)
                throw new ArgumentOutOfRangeException("arrayIndex");
            Contract.EndContractBlock();

            return Task.Run(() =>
            {
                foreach (var pair in this)
                    array[arrayIndex++] = pair;
            });
        }

        Task<bool> ICollectionAsync<KeyValuePair<TKey, TValue>>.RemoveAsync(KeyValuePair<TKey, TValue> item)
        {
            if (item.Key == null)
                throw new ArgumentNullException("item.Key");
            Contract.EndContractBlock();

            return _stringDictionary.RemoveAsync(_toString(item.Key));
        }

        public IDisposable Subscribe(IObserver<KeyValuePair<TKey, TValue>> observer)
        {
            throw new NotImplementedException("NYI");
        }

        #endregion IAsyncDictionary<TKey, TValue> members

        #region IDictionary<TKey, TValue> members

        public void Add(TKey key, TValue value)
        {
            _stringDictionary.Add(_toString(key), value);
        }

        public bool ContainsKey(TKey key)
        {
            return _stringDictionary.ContainsKey(_toString(key));
        }

        public bool Remove(TKey key)
        {
            return _stringDictionary.Remove(_toString(key));
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            var result = _stringDictionary.TryGetValue(_toString(key), out value);
            Contract.Assume(result == this.ContainsKey(key), "TryGetValue and ContainsKey don't agree on whether the key is in the dictionary."); // The static checker does not know the relation between key, _toString(key), and ContainsKey
            return result;
        }

        public TValue this[TKey key]
        {
            get
            {
                return _stringDictionary[_toString(key)];
            }
            set
            {
                _stringDictionary[_toString(key)] = value;
            }
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
        {
            _stringDictionary.Add(new KeyValuePair<string, TValue>(_toString(item.Key), item.Value));
        }
        
        public void Clear()
        {
            _stringDictionary.Clear();
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
        {
            return _stringDictionary.ContainsKey(_toString(item.Key));
        }

        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            if (arrayIndex < 0 || arrayIndex >= array.Length)
                throw new ArgumentOutOfRangeException("arrayIndex");
            if (arrayIndex + Count > array.Length)
                throw new ArgumentException(String.Format("Not enough room in the array"));

            foreach (var pair in this)
                array[arrayIndex++] = pair;
        }

        public int Count
        {
            get { return _stringDictionary.Count; }
        }

        public bool IsReadOnly
        {
            get { return _stringDictionary.IsReadOnly; }
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
        {
            return _stringDictionary.Remove(_toString(item.Key));
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return new CloudDictionaryEnumerator(_stringDictionary.GetEnumerator(), _fromString);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion IDictionary<TKey, TValue> members

        private struct CloudDictionaryEnumerator : IEnumerator<KeyValuePair<TKey, TValue>>
        {
            private IEnumerator<KeyValuePair<String, TValue>> _enumerator;
            private Func<String, TKey> _fromString;

            internal CloudDictionaryEnumerator(IEnumerator<KeyValuePair<String, TValue>> stringEnumerator, Func<String, TKey> fromStringFunction)
            {
                _enumerator = stringEnumerator;
                _fromString = fromStringFunction;
            }

            public KeyValuePair<TKey, TValue> Current
            {
                get {
                    KeyValuePair<String, TValue> pair = _enumerator.Current;
                    return new KeyValuePair<TKey, TValue>(_fromString(pair.Key), pair.Value);
                }
            }

            public void Dispose()
            {
                if (_enumerator != null)
                {
                    _enumerator.Dispose();
                    _enumerator = null;
                    _fromString = null;
                }
            }

            object System.Collections.IEnumerator.Current
            {
                get { return ((System.Collections.IEnumerator)_enumerator).Current; }
            }

            public bool MoveNext()
            {
                return _enumerator.MoveNext();
            }

            public void Reset()
            {
                _enumerator.Reset();
            }
        }        
    }
}
